package com.atsmart.app.http;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.Queue;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

import com.atsmart.app.R;
import com.atsmart.app.cache.CacheStore;
import com.atsmart.app.handler.ImageLoadedHandler;
import com.atsmart.app.utils.StringUtil;

public class ImageLoader {

    public static int SCREEN_VERICAL  = 0;
    public static int SCREEN_HORIZONAL= 1;
    public static int SCREEN_ORIGNAL  = 2;

    public static int IMAGE_KEEP_MEMORY  = 0;
    public static int IMAGE_NEED_RECYCLE = 1;
    public static int ROUND_CORNAL_IMAGE_NEED_RECYCEL = 2;
    //need recycled when no use, for example no display currently
    private HashMap<String, SoftReference<BitmapAttachImageViews>> cache = new HashMap<String, SoftReference<BitmapAttachImageViews>>();
    private static final int HARD_CACHE_CAPACITY = 400;
    private static final float CACHE_UP_LIMIT_SCALE = 0.75f;
    private static HashMap<String, SoftReference<Bitmap>> cacheSmallImages = new HashMap<String, SoftReference<Bitmap>>(
            HARD_CACHE_CAPACITY);
    final int stub_small_id = R.drawable.grid_mask;
    private PhotosQueue photosQueue = null;
    private PhotosLoader photoLoaderThread  = null;
    private boolean bFinished_ = true;

    private static ImageLoader sImageLoader = new ImageLoader();

    public static ImageLoader getInstance(){
        return sImageLoader;
    }

    private ImageLoader() {
    }

    private boolean getThreadStatus(){
        synchronized(this){
            return bFinished_;
        }
    }

    private void setThreadStatus(boolean bNewState){
        synchronized(this){
            bFinished_ = bNewState;
        }
    }

    public void displayGameImageWithLocal(String url, ImageView imageView){
        synchronized (cacheSmallImages){
            if (cacheSmallImages.containsKey(url)) {
                SoftReference<Bitmap> softReference = cacheSmallImages.get(url);
                Bitmap bm = softReference.get();
                if (bm != null && !bm.isRecycled()){
                    imageView.setImageBitmap(softReference.get());
                    return;
                }
                else {
                    cacheSmallImages.remove(url);
                    softReference = null;
                }
            }
        }

        setDefaultImage(IMAGE_KEEP_MEMORY, imageView);
    }

    public void displayImage(int cacheType, String url, ImageView imageView, int displayScreenType){
        displayImage(cacheType, url, imageView, displayScreenType, null);
    }

    /**
     * add callback
     * @param cacheType
     * @param url
     * @param imageView
     * @param displayScreenType
     * @param callbak
     */
    public void displayImage(int cacheType, String url, ImageView imageView, int displayScreenType, ImageCallbak callbak){
        if (StringUtil.isEmptyOrNull(url)){//protect
            return;
        }

        if (cacheType == IMAGE_KEEP_MEMORY || cacheType == ROUND_CORNAL_IMAGE_NEED_RECYCEL){
            displayImage(cacheType, url, imageView, callbak);
        }else{
            displayNeedRecycleImage(url, imageView, displayScreenType);
        }
    }

    private class BitmapAttachImageViews{
        Bitmap bm;
        ArrayList<WeakReference<ImageView>> arrImageList = new ArrayList<WeakReference<ImageView>>();;
    }

    private void displayNeedRecycleImage(String url, ImageView imageView, int displayScreenType){
        synchronized (cache){
            if (cache.containsKey(url)) {
                SoftReference<BitmapAttachImageViews> softReference = cache.get(url);
                Bitmap bm = softReference.get() != null ? softReference.get().bm : null;
                if (bm != null && !bm.isRecycled()){
                    imageView.setImageBitmap(bm);
                    softReference.get().arrImageList.add(new WeakReference<ImageView>(imageView));
                    return;
                }
                else {
                    cache.remove(url);
                }
            }
        }

        queuePhoto(url, imageView, IMAGE_NEED_RECYCLE, displayScreenType);
        setDefaultImage(IMAGE_NEED_RECYCLE, imageView);
    }

    private void setDefaultImage(int  CacheType, ImageView imageView) {
        imageView.setImageResource(stub_small_id);
    }

    private void displayImage(int cacheType, String url, ImageView imageView, ImageCallbak callbak) {
        synchronized (cacheSmallImages){
            if (cacheSmallImages.containsKey(url)) {
                SoftReference<Bitmap> softReference = cacheSmallImages.get(url);
                Bitmap bm = softReference.get();
                if (bm != null && !bm.isRecycled()){
                    if(callbak == null) {
                        imageView.setImageBitmap(softReference.get());
                    }else {
                        callbak.setResource(imageView,bm);
                    }
                    return;
                }
                else {
                    cacheSmallImages.remove(url);
                    softReference = null;
                }
            }
        }

        queuePhoto(url, imageView, cacheType, SCREEN_ORIGNAL, callbak);
        imageView.setImageResource(stub_small_id);
    }

    private void queuePhoto(String url, ImageView imageView, int cacheType, int displayScreenType) {
        queuePhoto(url, imageView, cacheType, displayScreenType, null);
    }

    private void queuePhoto(String url, ImageView imageView, int cacheType, int displayScreenType, ImageCallbak callbak) {
        // This ImageView may be used for other images before. So there may be
        // some old tasks in the queue. We need to discard them.
        if (photosQueue == null){
            photosQueue = new PhotosQueue();
        }else{
            photosQueue.clear(imageView);
        }

        PhotoToLoad p = new PhotoToLoad(url, imageView, cacheType, displayScreenType, callbak);
        synchronized (photosQueue.photosToLoad) {
            photosQueue.photosToLoad.offer(p);
            photosQueue.photosToLoad.notifyAll();
        }

        // start thread if it's not started yet
        if (getThreadStatus()){
            photoLoaderThread = new PhotosLoader();
            photoLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);
            photoLoaderThread.start();
            setThreadStatus(false);
        }
    }

    private Bitmap getBitmap(String url, int displayScreenType) {
        // from SD cache
        Bitmap b = decodeFileByURL(url, displayScreenType);
        if (b != null){
            return b;
        }
        // from web
        Bitmap bitmap   = null;
        InputStream is  = null;
        OutputStream os = null;
        try {
            is = new URL(url).openStream();
            CacheStore.getInstance().put(url, is);
            bitmap = decodeFileByURL(url, displayScreenType);
            return bitmap;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
        finally{
            if (is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (os != null){
                try {
                    os.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    private Bitmap getRoundedCornerBitmap(Bitmap bitmap) {
        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
                bitmap.getHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(output);

        final int color = 0xff424242;
        final Paint paint = new Paint();
        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        final RectF rectF = new RectF(rect);
        final float roundPx = 12;

        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(color);
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);
        return output;
    }

    // decodes image and scales it to reduce memory consumption
    private Bitmap decodeFileByURL(String keyWithURL, int displayScreenType) {
        FileInputStream fileInputStream = null;
        try {
            // decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            fileInputStream = (FileInputStream) CacheStore.getInstance().getReadStream(keyWithURL);
            if (fileInputStream == null){//no exist file
                return null;
            }
            BitmapFactory.decodeStream(fileInputStream, null, o);
            fileInputStream.close();

            // Find the correct scale value. It should be the power of 2.
            final int REQUIRED_SIZE = 270;
            int width_tmp = o.outWidth, height_tmp = o.outHeight;
            int scale = 1;
            while (true) {
                if (width_tmp / 2 < REQUIRED_SIZE
                        || height_tmp / 2 < REQUIRED_SIZE)
                    break;
                width_tmp /= 2;
                height_tmp /= 2;
                scale++;
            }

            // decode with inSampleSize
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize = scale;
            fileInputStream = (FileInputStream) CacheStore.getInstance().getReadStream(keyWithURL);
            Bitmap bm = BitmapFactory.decodeStream(fileInputStream, null, o2);
            if (displayScreenType == SCREEN_VERICAL){
                if(width_tmp > height_tmp*1.2 && bm!=null){
                    //for game info screenshot show
                    Matrix m = new Matrix();
                    m.postRotate(-90);
                    bm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), m, true);
                }
            }else if(displayScreenType == SCREEN_HORIZONAL){
                if(height_tmp > width_tmp*1.2 && bm!=null){
                    //for game info screenshot show
                    Matrix m = new Matrix();
                    m.postRotate(-90);
                    bm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), m, true);
                }
            }

            return bm;
        } catch (FileNotFoundException e) {
            Log.d("ggg", e.toString());
        } catch (IOException e){
            e.printStackTrace();
        }
        finally{
            if (fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    // Task for the queue
    public class PhotoToLoad {
        final public int imageDisplayType;
        final public int cacheType;
        final public String url;
        final public WeakReference<ImageView> imageView;
        final public ImageCallbak callback;

        public PhotoToLoad(String u, ImageView i) {
            imageDisplayType = SCREEN_ORIGNAL;
            url = u;
            imageView = new WeakReference<ImageView>(i);
            cacheType = IMAGE_KEEP_MEMORY;
            callback = null;
        }

        public PhotoToLoad(String u, ImageView i, int ct, int idt, ImageCallbak icb) {
            imageDisplayType = idt;
            url = u;
            imageView = new WeakReference<ImageView>(i);
            cacheType = ct;
            callback  = icb;
        }
    }

    public void stopThread() {
        if (photoLoaderThread != null) {
            if (photosQueue.photosToLoad != null) {
                synchronized (photosQueue.photosToLoad) {
                    photosQueue.photosToLoad.notifyAll();
                }
            }
        }
    }

    // stores list of photos to download
    private class PhotosQueue {
        private Queue<PhotoToLoad> photosToLoad = new LinkedList<PhotoToLoad>();

        // removes all instances of this ImageView
        public void clear(ImageView image) {
            if (photosToLoad.size() != 0) {
                synchronized (photosQueue.photosToLoad) {
                    Iterator<PhotoToLoad> it = photosToLoad.iterator();
                    while (it.hasNext()) {
                        PhotoToLoad photoToLoad = it.next();
                        if (photoToLoad != null
                                && photoToLoad.imageView != null) {
                            ImageView tmpImageView = photoToLoad.imageView
                                    .get();
                            if (tmpImageView != null && tmpImageView == image) {
                                it.remove();
                                break;
                            }
                        }
                    }
                }
            }
        }
    }

    class PhotosLoader extends Thread {
        public void run() {
            try {
                while (true) {
                    // thread waits until there are any images to load in the queue
                    if (photosQueue.photosToLoad.size() == 0)
                        synchronized (photosQueue.photosToLoad) {
                            photosQueue.photosToLoad.wait(60*1000);
                        }
                    if (photosQueue.photosToLoad.size() != 0) {
                        PhotoToLoad photoToLoad;
                        synchronized (photosQueue.photosToLoad) {
                            photoToLoad = photosQueue.photosToLoad.poll();
                        }

                        if (photoToLoad.imageView.get() != null){
                            Bitmap bmp = getBitmap(photoToLoad.url, photoToLoad.imageDisplayType);
                            if (bmp == null){
                                continue;
                            }

                            if (photoToLoad.cacheType == ROUND_CORNAL_IMAGE_NEED_RECYCEL){
                                bmp = getRoundedCornerBitmap(bmp);
                            }
                            ImageView tmpImageView = photoToLoad.imageView.get();
                            if (tmpImageView != null){//double check
                                if (photoToLoad.cacheType == IMAGE_NEED_RECYCLE) {
                                    synchronized (cache) {
                                        BitmapAttachImageViews cacheItem = new BitmapAttachImageViews();
                                        cacheItem.bm = bmp;
                                        cacheItem.arrImageList.add(new WeakReference<ImageView>(tmpImageView));
                                        cache.put(photoToLoad.url, new SoftReference<BitmapAttachImageViews>(cacheItem));
                                    }
                                } else {
                                    synchronized (cacheSmallImages) {
                                        if (cacheSmallImages.size() < HARD_CACHE_CAPACITY
                                                * CACHE_UP_LIMIT_SCALE) {
                                        cacheSmallImages.put(photoToLoad.url,
                                                new SoftReference<Bitmap>(bmp));
                                        }
                                    }
                                }
                                if (((String) tmpImageView.getTag())
                                        .equals(photoToLoad.url)) {
                                    ImageLoadedHandler.getInstance().notifyLoaded(tmpImageView, bmp, photoToLoad.callback);
                                }
                            }
                        }
                    }else{
                        break;
                    }
                }
            } catch (InterruptedException e) {
                // allow thread to exit
                Log.d("ggg", e.toString());
            }
            finally{
                if (photosQueue.photosToLoad.size() != 0) {
                    synchronized (photosQueue.photosToLoad) {
                        photosQueue.photosToLoad.clear();
                    }
                }

                setThreadStatus(true);
            }
        }
    }


    public void clearCache() {
        // clear memory cache
        synchronized (cache){
            Iterator<Entry<String, SoftReference<BitmapAttachImageViews>>> it = cache.entrySet().iterator();
            while (it.hasNext()){
                BitmapAttachImageViews bitmapAttachImageViews = it.next().getValue().get();
                if (bitmapAttachImageViews != null){
                    boolean bHolder = false;
                    for (WeakReference<ImageView> each : bitmapAttachImageViews.arrImageList){
                        if (each.get() != null && ((ImageView)each.get()).getWindowVisibility() == View.VISIBLE){
                            bHolder = true;
                            break;
                        }
                    }

                    if (!bHolder){
                        Bitmap bm = bitmapAttachImageViews.bm;
                        if(bm != null && !bm.isRecycled()){
                           bm.recycle();
                        }
                        it.remove();
                    }
                }
            }
        }
    }

    public void clearCache(String imageUrl){
        if(StringUtil.isEmptyOrNull(imageUrl)) {
            return;
        }
        synchronized (cache){
            SoftReference<BitmapAttachImageViews> reference = cache.get(imageUrl);
            if(reference == null) {
                return;
            }

            BitmapAttachImageViews bitmapAttachImageView = reference.get();
            for (WeakReference<ImageView> each : bitmapAttachImageView.arrImageList){
                if (each.get() != null && ((ImageView)each.get()).getWindowVisibility() == View.VISIBLE){
                    return;
                }
            }
            Bitmap bitmap = bitmapAttachImageView.bm;
            cache.remove(imageUrl);
            if(bitmap == null) {
                return;
            }
            bitmap.recycle();
        }
    }

    public interface ImageCallbak{
        public void setResource(ImageView view,Bitmap bitmap);
    }
}
